package org.tulkas.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * Utility class to handle file related operations
 * 
 * @author <a href="mailto:tulkas.web@gmail.com">Tulkas</a>
 *
 */
public abstract class FileUtil {
	private static int BUFFER_SIZE = 1024;

	/**
	 * create a new File
	 *
	 * @param fileName, file name
	 * @param Override, if the file exist, override or not.
	 * @throws IOException
	 */
	public static boolean createFile(String fileName, boolean Override)
			throws IOException {
		File file = new File(fileName);
		File dir = new File(file.getParent());
		dir.mkdirs();
		if (Override)
			file.delete();
		return file.createNewFile();
	}

	/**
	 * copy a file
	 *
	 * @param src, full path of the source file
	 * @param des, full path of the destination file
	 * @throws IOException
	 */
	public static void copyFile(String src, String des) throws IOException {
		FileInputStream in = new FileInputStream(src);
		OutputStream out = new FileOutputStream(des);
		readAndWrite(in, out);
		out.close();
		in.close();
	}

	/**
	 * read the data bytes from an inputStream to an outputStream, until there is no byte that can be read.
	 *
	 * @param in
	 * @param out
	 * @throws IOException
	 */
	public static void readAndWrite(InputStream in, OutputStream out)
			throws IOException {
		byte[] buffer = new byte[BUFFER_SIZE];
		int read;
		while (true) {
			read = in.read(buffer);
			if (read <= 0)
				break;
			out.write(buffer, 0, read);
		}
	}

	/**
	 * read the data chars from a reader to a writer, until there is no char that can be read.
	 * 
	 * @param in
	 * @param out
	 * @throws IOException
	 */
	public static void readAndWrite(Reader in, Writer out) throws IOException {
		char[] buffer = new char[BUFFER_SIZE];
		int read;
		while (true) {
			read = in.read(buffer);
			if (read <= 0)
				break;
			out.write(buffer, 0, read);
		}
	}

	/**
	 * read the data from an inputstream into a byte array.
	 * 
	 * @param in
	 * @return
	 * @throws IOException
	 */
	public static byte[] readBytes(InputStream in) throws IOException {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		readAndWrite(in, out);
		byte[] bytes = out.toByteArray();
		out.close();
		return bytes;
	}

	/**
	 * write the data from a byte array into an outputStream.
	 * 
	 * @param out
	 * @param bytes
	 * @throws IOException
	 */
	public static void writeBytes(OutputStream out, byte[] bytes)
			throws IOException {
		ByteArrayInputStream in = new ByteArrayInputStream(bytes);
		readAndWrite(in, out);
		in.close();
	}

	/**
	 * read the chars from a reader and construct a string from the chars.
	 * 
	 * @param in
	 * @return
	 * @throws IOException
	 */
	public static String readString(Reader in) throws IOException {
		StringWriter out = new StringWriter();
		readAndWrite(in, out);
		String ret = out.toString();
		out.close();
		return ret;
	}

	/**
	 * write the chars in a string into a writer.
	 * 
	 * @param out
	 * @param string
	 * @throws IOException
	 */
	public static void writeString(Writer out, String str) throws IOException {
		StringReader in = new StringReader(str);
		readAndWrite(in, out);
		in.close();
	}

	/**
	 * delete all files and directories in the path,include subdirectories,
	 *  include the path itself.
	 *
	 * @param path, the path of the root folder.
	 */
	public static void delTree(String path) {
		File pathFile = new File(path);
		if (!pathFile.exists())
			return;
		File[] files = pathFile.listFiles();
		for (int i = 0; i < files.length; i++) {
			if (files[i].isDirectory())
				delTree(files[i].getAbsolutePath());
			else {
				files[i].delete();
			}
		}
		pathFile.delete();

	}

	/**
	 * delete all files with the specific suffix in the path. This method searches all the subdirectories
	 *
	 * @param path, the path of the root folder
	 * @param suffix, the suffix of a file name. should not contain a '.'
	 */
	public static void delTree(String path, final String suffix) {
		File pathFile = new File(path);
		if (!pathFile.exists())
			return;
		File[] files = pathFile.listFiles(new FilenameFilter() {
			public boolean accept(File dir, String name) {
				return name.endsWith(suffix);
			}
		});
		for (int i = 0; i < files.length; i++) {
			files[i].delete();
		}

		files = pathFile.listFiles();
		for (int i = 0; i < files.length; i++) {
			if (files[i].isDirectory())
				delTree(files[i].getAbsolutePath(), suffix);
		}
	}

	/**
	 * unzip the data from a ZipInputStream into a destination directory.
	 * Will build all the folder structure according to the zip entries.
	 *
	 * @param in
	 * @param destDir
	 * @throws IOException
	 */
	public static void unzip(ZipInputStream in, String destDir)
			throws IOException {

		OutputStream out;
		ZipEntry entry;
		File dest = new File(destDir);
		if (!dest.exists())
			dest.mkdir();

		destDir = trimFilePath(destDir);
		while ((entry = in.getNextEntry()) != null) {
			String name = entry.getName();

			if (entry.isDirectory()) {
				File dir = new File(destDir + "/" + name);
				if (!dir.exists())
					dir.mkdirs();
			} else {
				int index_java = name.lastIndexOf("/");
				int index_win = name.lastIndexOf("\\");
				int index = -1;
				File dir = null;
				if ((index = index_java) != -1 || (index = index_win) != -1)
					dir = new File(destDir + "/" + name.substring(0, index));
				else
					dir = new File(destDir);

				if (!dir.exists())
					dir.mkdirs();
				File file = new File(destDir + "/" + name);
				if (file.isDirectory()) {
					continue;
				}
				out = new FileOutputStream(file);
				readAndWrite(in, out);
				out.close();
				long time = entry.getTime();
				if (time > 0)
					file.setLastModified(time);
			}
		}
	}

	/**
	 *  Unzip a zip file into a destination directory.
	 *  Will build all the folder structure according to the zip entries.
	 *
	 * @param srcFile
	 * @param destDir
	 * @throws IOException
	 */
	public static void unzip(String srcFile, String destDir) throws IOException {
		if (!new File(srcFile).exists()) {
			throw new FileNotFoundException(srcFile);
		}
		FileInputStream fis = new FileInputStream(srcFile);
		ZipInputStream in = new ZipInputStream(fis);
		unzip(in, destDir);
		in.close();
	}

	/**
	 * zip all the files and directories in the srcDir into a ZipOutputStream.
	 * The srcDir will not be included in the zip entries.
	 *
	 * @param srcDir
	 * @param stream
	 * @throws IOException 
	 */
	public static void zip(String srcDir, ZipOutputStream stream)
			throws IOException {
		zip_(srcDir, "", stream);
	}

	private static void zip_(String srcDir, String entryPrefix,
			ZipOutputStream stream) throws IOException {
		File dir = new File(srcDir);
		if (!dir.isDirectory())
			return;

		File[] files = dir.listFiles();
		for (int i = 0; i < files.length; i++) {
			File file = files[i];
			if (file.isDirectory()) {
				String prefix = null;
				if (entryPrefix == null || entryPrefix.equals(""))
					prefix = file.getName();
				else
					prefix = entryPrefix + "/" + file.getName();
				zip_(file.getAbsolutePath(), prefix, stream);
			} else {
				String name = null;
				if (entryPrefix == null || entryPrefix.equals(""))
					name = file.getName();
				else
					name = entryPrefix + "/" + file.getName();

				ZipEntry entry = new ZipEntry(name);
				entry.setSize(file.length());
				entry.setTime(file.lastModified());

				stream.putNextEntry(entry);
				FileInputStream fis = new FileInputStream(file);
				readAndWrite(fis, stream);
				fis.close();
				stream.closeEntry();
			}
		}
	}

	/**
	 * zip all the files and directories in the srcDir into a zip file.
	 * The srcDir will not be included in the zip entries.
	 *
	 * @param srcDir
	 * @param destFile
	 */
	public static void zip(String srcDir, String destFile) throws IOException {
		if (!new File(srcDir).exists()) {
			throw new FileNotFoundException(srcDir);
		}
		FileOutputStream fos = new FileOutputStream(destFile);
		String dir = getFileDir(destFile);
		if (dir != null)
			new File(dir).mkdirs();

		ZipOutputStream zos = new ZipOutputStream(fos);
		zip(srcDir, zos);
		zos.close();
	}

	/**
	 * get the suffix of a file name, not include the '.'.<BR/>
	 * e.g. the suffix of /root/aaa.bbb is bbb 
	 *
	 * @param filePath
	 * @return the suffix, not include the '.'
	 */
	public static String getFileSuffix(String filePath) {
		if (filePath == null)
			return null;
		int index = filePath.lastIndexOf('.');
		if (index == -1)
			return null;

		return filePath.substring(index + 1);
	}

	/**
	 * get the lowercase of the suffix of a file name, not include the '.'.<BR/>
	 * e.g. the suffix of /root/aaa.BBB is bbb 
	 *
	 * @param filePath
	 * @return
	 */
	public static String getFileSuffixLc(String filePath) {
		String suffix = getFileSuffix(filePath);
		if (suffix == null)
			return null;

		return suffix.toLowerCase();
	}

	/**
	 * get the parent directory path of a file path.<BR/>
	 * e.g. the file dir of /root/aaa/bbb.ccc is /root/aaa
	 * 
	 * @param filePath
	 * @return
	 */
	public static String getFileDir(String filePath) {
		if (filePath == null)
			return null;
		String fileName = trimFilePath(filePath);
		int index = fileName.lastIndexOf('/');
		if (index == -1)
			return null;
		else
			return fileName.substring(0, index);
	}

	/**
	 * get the filename of a full filepath<BR/>
	 * e.g. the file name of /root/aaa/bbb.ccc is bbb.ccc
	 * 
	 * @param filePath
	 * @return
	 */
	public static String getFileName(String filePath) {
		if (filePath == null)
			return null;
		String fileName = trimFilePath(filePath);
		int index = fileName.lastIndexOf('/');
		return fileName.substring(index + 1);
	}

	/**
	 * get the filename without the suffix of a full filepath<BR/>
	 * e.g. the pure file name of /root/aaa/bbb.ccc is bbb
	 * 
	 * @param filePath
	 * @return
	 */
	public static String getPureFileName(String filePath) {
		if (filePath == null)
			return null;
		String fileName = getFileName(filePath);
		int index = fileName.lastIndexOf('.');
		if (index == -1)
			return fileName;

		return fileName.substring(0, index);
	}

	/**
	 * Trim the filepath into a fixed format. Turns all the '\' into '/' and remove all the '/' or '\' in the end of the path.<BR/>
	 * e.g. c:\aaa\bbb\ccc\ will be trimed as c:/aaa/bbb/ccc
	 *
	 * @param filePath
	 * @return
	 */
	public static String trimFilePath(String filePath) {
		if (filePath == null)
			return null;
		String ret = filePath;
		if (ret.length() < 1)
			return ret;
		//		while (true) {
		//			char c = ret.charAt(0);
		//			if (c == '/' || c == '\\')
		//				ret = ret.substring(1);
		//			else
		//				break;
		//		}

		while (ret.length() > 0) {
			char c = ret.charAt(ret.length() - 1);
			if (c == '/' || c == '\\')
				ret = ret.substring(0, ret.length() - 1);
			else
				break;
		}
		ret = ret.replaceAll("\\\\", "/");
		return ret;
	}

	public static final String NAME_NUMBER_SPLITTER = "_";

	/**
	 * make a unique name with a incremental number suffix according to an existing name in the same format.<BR/>
	 * e.g. filename_1 will get a result as filename_2. filename will get a result as fulename_1
	 * 
	 * @param nameExist
	 * @return
	 */
	public static String makeUniqueNameWithNumber(String nameExist) {
		if (nameExist == null)
			return null;
		String[] names = nameExist.split(NAME_NUMBER_SPLITTER);
		if (names.length < 2)
			return nameExist + NAME_NUMBER_SPLITTER + 1;
		int suffix = 0;
		try {
			suffix = Integer.parseInt(names[names.length - 1]);
		} catch (NumberFormatException e) {
		}
		suffix++;
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < names.length - 1; i++)
			sb.append(names[i]);
		return sb.append(NAME_NUMBER_SPLITTER).append(suffix).toString();
	}

	/**
	 * get all the child folders of a folder.
	 * 
	 * @param dirFile, the path of the parent folder.
	 * @return
	 */
	public static File[] getChildDirs(File dirFile) {
		if (dirFile == null)
			return null;
		File[] files = dirFile.listFiles(new FileFilter() {
			public boolean accept(File pathname) {
				if (pathname.isDirectory())
					return true;
				return false;
			}
		});
		return files;
	}

	public static void muteClose(Closeable closeable) {
		try {
			if (closeable != null) {
				closeable.close();
			}
		} catch (IOException e) {
		}
	}

}
